Python编译和运行

您所在的位置:网站首页 python 编译语言 Python编译和运行

Python编译和运行

2023-08-22 23:17| 来源: 网络整理| 查看: 265

Python虽然是一门解释型语言,但Python程序执行时,也需要将源码进行编译生成字节码,然后由Python虚拟机进行执行,因此Python解释器实际是由两部分组成:编译器和虚拟机。 Python程序执行过程和Java类似,都是先将代码编译成字节码,然后由虚拟机执行:

Python执行过程

编译Python程序

在Java中,使用javac命令调用编译器(JavaCompile)将.java文件编译成字节码并输出.class字节码文件,然后使用java命令通过JVM执行编译后的字节码文件;

在Python中,由于编译器和虚拟机合二为一,所以没有区分编译器和虚拟机运行的命令,使用python命令调用python解释器,默认就会将.py文件编译并运行。

py_compile.compile函数

但既然需要编译后运行,那当然会有编译功能模块,python可以调用compileall.py或py_compile.py模块来编译.py文件并生成.pyc字节码文件

compileall.py模块

compileall.py模块有compile_dir、compile_file两个个函数,用于对指定目录或文件进行编译。通过Python命令参数调用时,compile_path函数会通过传入路径参数判断编译目标是目录还是文件 在桌面编写demo.py程序,使用python -m compileall demo.py编译:

compileall编译py文件 编译完成后,在demo.py同级目录下生成了一个__pycache__目录,目录下有一个demo.cpython-37.pyc文件(python使用Python3.7版本): 生成的pyc文件 更多关于compileall的内容详见:https://docs.python.org/zh-cn/3.7/library/compileall.html

py_compile.py模块

compileall内部最终调用的是py_compile.py模块的compile函数,compile函数是最终完成代码编译并输出字节码文件的函数,因此也可以使用python -m py_compile demo.py对代码进行编译:

py_compile编译py文件 更多关于py_compile的内容详见:https://docs.python.org/zh-cn/3.7/library/py_compile.html

内建函数compile

python还有一个内建函数compile,用于将源码编译成代码或 AST 对象。代码对象可以被 exec() 或 eval() 执行。源码可以是常规的字符串、字节字符串,或者 AST 对象。bltinmodule.c中compile函数的定义:

/* source: object filename: object(converter="PyUnicode_FSDecoder") mode: str flags: int = 0 dont_inherit: bool(accept={int}) = False optimize: int = -1 * _feature_version as feature_version: int = -1 将源代码编译成可由exec()或eval()执行的代码对象 source: 源代码可以是一个Python模块、语句或表达式 filename: 文件名将用于运行时错误消息 mode: 编译模块的模式必须是'exec',编译单个(交互式)语句的模式必须是'single', 编译表达式的模式必须是'eval' ... */ static PyObject * builtin_compile_impl(PyObject _module, PyObject_ source, PyObject *filename, const char *mode, int flags, int dont_inherit, int optimize, int feature_version) { ...C语言源码实现位于cpython/Python/bltinmodule.c,有兴趣的可以去github查看完整源码 }

compile函数使用方法:

compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1)

下面来看看compile的用法:

>>> source = 'for i in range(3): print(i)' >>> result = compile(source, '', 'exec') >>> result

可以看到,编译后返回了一个CodeObject对象,这个对象是可以被exec函数执行的:

>>> exec(result) 0 1 2 编译运行过程

从上述内容可知,py_compile.py模块中的compile函数由python实现,只能对.py文件进行编译,编译完成后返回字节码文件路径; 而内建函数compile由C语言实现,可以对代码语句进行编译,编译完成后返回一个CdoeObject对象。 那这个CodeObject对象是什么呢? 在上述的compile源码中,可以看到函数返回的是PyObject,这是返回给Python的包装对象,其内部是PyCodeObject,PyCodeObject对象如下:

/* _Bytecode object_ */ struct PyCodeObject { PyObject_HEAD / _Python定长对象头_ / PyObject *co_consts; /* 常量列表 */ PyObject *co_names; /* 名称列表(常量名、函数名、类名等) */ PyObject *co_code; /* 指令操作码(字节码) */ PyObject *co_filename; /* 源文件名 */ ...完整属性请查看cpython/Include/cpython/code.h };

查看result的属性值:

>>> result.co_names ('range', 'i', 'print') >>> result.co_consts (3, None) >>> result.co_code b'x\x18e\x00d\x00\x83\x01D\x00]\x0cZ\x01e\x02e\x01\x83\x01\x01\x00q\nW\x00d\x01S\x00' >>> result.co_filename ''

其中co_code属性是不可读的bytes数据,这时候就要用到Python内置的一个模块——dis,dis 模块通过反汇编支持CPython的 bytecode 分析。 通过dis模块的disco函数,可反汇编代码对象

disco(code, lasti=-1, ***, file=None) 其中参数code是代码对象

disco方法返回字节码操作的格式化字符,描述了Python解释器将要执行的指令,内容类似于汇编语言,内容分为以下几列:

行号,用于每行的第一条指令 当前指令,表示为 --> , 一个指令 >> 表示, 指令的地址, 操作码名称, 操作参数,和括号中的参数解释。

例如:

>>> import dis >>> dis.disco(result) 1 0 SETUP_LOOP 24 (to 26) 2 LOAD_NAME 0 (range) 4 LOAD_CONST 0 (3) 6 CALL_FUNCTION 1 8 GET_ITER >> 10 FOR_ITER 12 (to 24) 12 STORE_NAME 1 (i) 14 LOAD_NAME 2 (print) 16 LOAD_NAME 1 (i) 18 CALL_FUNCTION 1 20 POP_TOP 22 JUMP_ABSOLUTE 10 >> 24 POP_BLOCK >> 26 LOAD_CONST 1 (None) 28 RETURN_VALUE

SETUP_LOOP 24 (to 26):将一个用于循环的块推到块堆栈上。该块从当前指令开始,其大小为24字节 LOAD_NAME 0 (0):将result.co_names[0]的值移到栈顶,该值是'range' LOAD_CONST 0 (0):将result.co_consts[0]移到栈顶,该值是3 CALL_FUNCTION 1:调用一个可调用对象并传入位置参数1 POP_TOP:删除栈顶元素,即删除TOS GET_ITER:实现TOS = iter(TOS),把 iter(TOS) 的结果推回堆栈 ...

>>> result.co_names[0] 'range' >>> result.co_consts[0] 3

综上所述,Python内建函数compile在编译某段源码时,不会直接返回字节码,而是返回一个CodeObject对象,该对象存储了程序运行所需的相关数据和程序运行过程。 更多操作码解释和dis模块的使用请查看:dis --- Python 字节码反汇编器

动态编译

Java程序运行时,会先编译所有Java文件并加载类,程序运行起来后无论修改或新增代码,都不会影响程序运行,如果需要在运行中加入新的代码,需要先调用编译器(JavaCompiler)编译代码,再调用类加载器(ClassLoader)将编译后的字节码加入当前的运行环境,这个过程就是动态编译。

在Python中,要实现程序运行后执行新增的代码要简单得多,Python提供一个内建函数exec,可以执行代码语句或者是经过compile编译后的代码对象:

>>> help(exec) Help on built-in function exec in module builtins: exec(source, globals=None, locals=None, /) Execute the given source in the context of globals and locals. The source may be a string representing one or more Python statements or a code object as returned by compile(). The globals must be a dictionary and locals can be any mapping, defaulting to the current globals and locals. If only globals is given, locals defaults to it.

所以,如果代码比较简单,可以直接调用exec执行Python语句字符串;如果代码比较复杂,可以先写入.py文件,调用compile编译代码后再调用exec执行。 综上所述,最多只需要调用两个方法,就可以实现Python的动态编译了。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3